eletrotupi / tcc/ frontend/app/sleep/[id].tsx22ce11e7f70ff2faf4796eef6498cd1ec65bbd31
7.4 KB | 292 lines Raw
1
import { useState, useEffect, use } from "react";
2
import {
3
  StyleSheet,
4
  View,
5
  ScrollView,
6
  Pressable,
7
  ActivityIndicator,
8
} from "react-native";
9
import { Text } from "@/components/ui/Text";
10
import { useLocalSearchParams, router, Stack } from "expo-router";
11
import { format } from "date-fns";
12
import { ptBR } from "date-fns/locale";
13

14
import { Card, SubtleInfoCard } from "@/components/ui/Cards";
15
import { Badge } from "@/components/ui/Badge";
16
import { Button } from "@/components/ui/Button";
17
import { Col, Between, Row } from "@/components/ui/LayoutHelpers";
18
import { Section, SectionHeader } from "@/components/ui/Sections";
19
import { Theme, Colors, Spacing } from "@/constants/theme";
20
import { MaterialIcons } from "@expo/vector-icons";
21
import {
22
  useSleepRecord,
23
  useDeleteSleepRecord,
24
} from "@/hooks/useSleepRecord.queries";
25
import { formatDate, parseDateOnly } from "@/lib/utils/time";
26

27
export default function SleepDetailScreen() {
28
  const { id } = useLocalSearchParams<{ id: string }>();
29
  const { data: sleepRecord, isLoading, isError } = useSleepRecord(id);
30
  const deleteSleepRecord = useDeleteSleepRecord();
31
  const [minutesLeft, setMinutesLeft] = useState(0);
32
  const [canDelete, setCanDelete] = useState(false);
33

34
  useEffect(() => {
35
    const updateTimer = () => {
36
      const createdAt = sleepRecord?.createdAt;
37
      if (!createdAt) return;
38

39
      const msSinceCreation = Date.now() - new Date(createdAt).getTime();
40
      const deletionWindowMs = 5 * 60 * 1000;
41

42
      const newCanDelete = msSinceCreation < deletionWindowMs;
43
      const newMinutesLeft = Math.max(
44
        0,
45
        Math.ceil((deletionWindowMs - msSinceCreation) / 60000),
46
      );
47

48
      setCanDelete(newCanDelete);
49
      setMinutesLeft(newMinutesLeft);
50
    };
51

52
    if (isLoading) return;
53

54
    updateTimer(); // Set initial values
55

56
    const interval = setInterval(updateTimer, 1000);
57

58
    return () => clearInterval(interval);
59
  }, [sleepRecord, isLoading]);
60

61
  if (isLoading) {
62
    return (
63
      <View style={styles.centered}>
64
        <ActivityIndicator size="large" color={Colors.light.tint} />
65
      </View>
66
    );
67
  }
68

69
  if (isError || !sleepRecord) {
70
    return (
71
      <View style={styles.centered}>
72
        <Text style={styles.errorText}>Registro não encontrado.</Text>
73
        <Pressable onPress={() => router.back()} style={styles.backButton}>
74
          <Text style={styles.backButtonText}>Voltar</Text>
75
        </Pressable>
76
      </View>
77
    );
78
  }
79

80
  const handleDelete = async () => {
81
    await deleteSleepRecord.mutateAsync(String(sleepRecord.id));
82

83
    router.back();
84
  };
85

86
  const deleteButtonMsg = () => {
87
    const msg = "Excluir Registro";
88
    const timeLeftStr = `(Disp. por ${minutesLeft < 1 ? "<1" : minutesLeft}:00)`;
89
    const suffix = canDelete ? timeLeftStr : "(Expirado)";
90

91
    return `${msg} ${suffix}`;
92
  };
93

94
  return (
95
    <>
96
      <Stack.Screen options={{ title: "Ver detalhes" }} />
97

98
      <ScrollView
99
        style={styles.container}
100
        contentContainerStyle={styles.content}
101
        showsVerticalScrollIndicator={false}
102
      >
103
        {/* Header card */}
104
        <Card style={styles.headerCard}>
105
          <Row style={{ alignItems: "center" }}>
106
            <View style={[styles.iconWrap, { backgroundColor: '#ede9fe' }]}>
107
              <MaterialIcons name="bedtime" size={22} color="#7c3aed" />
108
            </View>
109
            <Col>
110
              <Row style={{ alignItems: "flex-end" }}>
111
                <Text style={styles.averageLabel}>{sleepRecord.average}h</Text>
112
                <Text style={styles.suffixLabel}>de sono</Text>
113
              </Row>
114

115
              <Text style={styles.dateLabel}>
116
                {formatDate(parseDateOnly(sleepRecord.date))}
117
              </Text>
118
            </Col>
119
          </Row>
120
        </Card>
121

122
        {/* Annotation / notes */}
123
        {sleepRecord.annotations && (
124
          <Section>
125
            <SectionHeader title="Anotações" variant="subtle" />
126
            <Card style={{ padding: Spacing.cardGap }}>
127
              <Text style={styles.annotation}>
128
                "{sleepRecord.annotations?.trim()}"
129
              </Text>
130
            </Card>
131
          </Section>
132
        )}
133

134
        {/* Delete */}
135
        <View style={styles.section}>
136
          <Text style={styles.sectionLabel}>GESTÃO DO REGISTRO</Text>
137
          <Button
138
            variant="outline"
139
            loading={false}
140
            disabled={!canDelete}
141
            onPress={() => router.push(`/sleep/edit/${sleepRecord.id}`)}
142
            title="Editar Registro"
143
          />
144

145
          <Button
146
            variant="danger"
147
            loading={deleteSleepRecord.isPending}
148
            disabled={!canDelete || deleteSleepRecord.isPending}
149
            onPress={handleDelete}
150
            title={deleteButtonMsg()}
151
          />
152

153
          <SubtleInfoCard
154
            style={styles.deleteInfo}
155
            text={`
156
              A exclusão de registros é permitida apenas nos primeiros 5 minutos
157
              após o envio para garantir a integridade do histórico emocional.
158
              `}
159
          />
160
        </View>
161
      </ScrollView>
162
    </>
163
  );
164
}
165

166
const styles = StyleSheet.create({
167
  container: {
168
    flex: 1,
169
    backgroundColor: Theme.colors.background,
170
  },
171
  content: {
172
    padding: Spacing.containerPadding,
173
    gap: Spacing.sectionGap,
174
    paddingBottom: 48,
175
  },
176
  iconWrap: {
177
    width: 40,
178
    height: 40,
179
    borderRadius: 20,
180
    alignItems: 'center',
181
    justifyContent: 'center',
182
    marginTop: 1
183
  },
184
  headerCardContent: {
185
    flexDirection: "row",
186
    alignItems: "center",
187
    gap: 8,
188
  },
189
  averageLabel: {
190
    height: '100%',
191
    fontSize: 24,
192
    fontWeight: "700",
193
    color: Colors.light.text,
194
  },
195
  averageLabelSuffix: {
196
    height: '100%',
197
    fontSize: 17,
198
    fontWeight: "700",
199
    color: Colors.light.textSecondary,
200
  },
201
  centered: {
202
    flex: 1,
203
    justifyContent: "center",
204
    alignItems: "center",
205
    gap: 12,
206
  },
207
  errorText: {
208
    fontSize: 15,
209
    color: Colors.light.textSecondary,
210
  },
211
  backButton: {
212
    paddingHorizontal: 20,
213
    paddingVertical: 10,
214
    backgroundColor: Theme.colors.tint,
215
    borderRadius: Theme.borderRadius.md,
216
  },
217
  backButtonText: {
218
    fontWeight: "700",
219
    color: Theme.colors.text,
220
  },
221

222
  // Header
223
  headerCard: {
224
    padding: 16,
225
  },
226
  moodIcon: {
227
    fontSize: 40,
228
  },
229
  moodLabel: {
230
    fontSize: 17,
231
    fontWeight: "700",
232
    color: Colors.light.text,
233
  },
234
  moodTime: {
235
    fontSize: 13,
236
    color: Colors.light.textSecondary,
237
  },
238

239
  // Sections
240
  section: {
241
    gap: 8,
242
  },
243
  sectionLabel: {
244
    fontSize: 11,
245
    fontWeight: "600",
246
    letterSpacing: 0.8,
247
    color: Colors.light.textSecondary,
248
    textTransform: "uppercase",
249
  },
250

251
  // Components
252
  componentRow: {
253
    flexDirection: "row",
254
    justifyContent: "space-between",
255
    alignItems: "center",
256
    paddingVertical: 10,
257
  },
258
  componentLabel: {
259
    fontSize: 15,
260
    fontWeight: "700",
261
    color: Colors.light.text,
262
  },
263
  componentIntensity: {
264
    fontSize: 13,
265
    color: Colors.light.textSecondary,
266
  },
267
  componentIntensityGreen: {
268
    fontSize: 13,
269
    fontWeight: "700",
270
    color: Theme.colors.tint,
271
  },
272
  progressTrack: {
273
    height: 6,
274
    backgroundColor: Theme.colors.light.divider,
275
    borderRadius: Theme.borderRadius.full,
276
    marginBottom: 4,
277
    overflow: "hidden",
278
  },
279
  progressFill: {
280
    height: "100%",
281
    backgroundColor: Theme.colors.light.tint,
282
    borderRadius: Theme.borderRadius.full,
283
  },
284
  // Annotation
285
  annotation: {
286
    fontSize: 14,
287
    lineHeight: 22,
288
    color: Colors.light.textSecondary,
289
    fontStyle: "italic",
290
    padding: 4,
291
  },
292
});